﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Foxit.PDF;
using Foxit.PDF.PageElements;
using Foxit.PDF.Imaging;
using Foxit.PDF.Merger;
using Sustainalytics.Entities;
using Sustainalytics.Reporting.Framework;

namespace Sustainalytics.ReportGenerator.BL
{
    // new ClientReport Pdf Renderer
    public partial class PdfReportRenderer
    {
        private readonly CompanyReportDataOffline _dataSource;
        private readonly bool _useParallel;
        private readonly bool _bulkGeneration;
        private readonly List<GenerationResult> _generationResults;
        private readonly List<OutlinePosition> _outlinePositions;

        # region Static Images

        private static byte[] _companyLogoHeaderImage;
        private static byte[] _companyLogoFooterImage;
        public byte[] CompanyLogoHeaderImage { get { return _companyLogoHeaderImage; } }
        public byte[] CompanyLogoFooterImage { get { return _companyLogoFooterImage; } }

        private static void LoadStaticImages()
        {
            if (_companyLogoHeaderImage == null)
                _companyLogoHeaderImage = ImagesGenerator.GetJpgImage(@"Images\sustaynalytics_logo_header.jpg");

            if (_companyLogoFooterImage == null)
                _companyLogoFooterImage = ImagesGenerator.GetJpgImage(@"Images\sustaynalytics_logo_footer.jpg");
        }

        #endregion

        static PdfReportRenderer()
        {
            LoadStaticImages();
        }

        public PdfReportRenderer() { }


        static void ValidateInput(string name, object obj, Guid id)
        {
            if (obj == null)
                throw new ArgumentException("CompanyReportDataOffline does not contain the necessary data for report generation: " + name + " " + id.ToString());
        }

        public PdfReportRenderer(CompanyReportDataOffline dataSource, bool useParallel, bool bulkGeneration)
        {
            if (dataSource == null || dataSource.CompanyId == null)
            {
                throw new ArgumentException("CompanyReportDataOffline does not contain the necessary data for report generation!");
            }

            if (dataSource.ReportType == ReportType.ComprehensiveReport || 
                dataSource.ReportType == ReportType.SummaryComprehensiveReport)
            {
                ValidateInput("CorporateData", dataSource.CorporateData, dataSource.CompanyId);
                ValidateInput("FinancialData", dataSource.FinancialData, dataSource.CompanyId);

                ValidateInput("OverallPerformance", dataSource.OverallPerformance, dataSource.CompanyId);
                ValidateInput("IndustryTopPerformers", dataSource.IndustryTopPerformers, dataSource.CompanyId);
                ValidateInput("IndustryPeers", dataSource.IndustryPeers, dataSource.CompanyId);

                ValidateInput("CompanyHistoricalPerformance", dataSource.CompanyHistoricalPerformance, dataSource.CompanyId);

                ValidateInput("Analyst", dataSource.Analyst, dataSource.CompanyId);
                ValidateInput("SubPeerGroupKeyEsgIssues", dataSource.SubPeerGroupKeyEsgIssues, dataSource.CompanyId);
                ValidateInput("KeyEsgIssues", dataSource.KeyEsgIssues, dataSource.CompanyId);

                ValidateInput("IndicatorTemplates", dataSource.IndicatorTemplates, dataSource.CompanyId);
                ValidateInput("ThemesPerformance", dataSource.ThemesPerformance, dataSource.CompanyId);
                ValidateInput("StandardIndicators", dataSource.StandardIndicators, dataSource.CompanyId);
                ValidateInput("ControversyIndicators", dataSource.ControversyIndicators, dataSource.CompanyId);
                ValidateInput("EventIndicators", dataSource.EventIndicators, dataSource.CompanyId);
                ValidateInput("Incidents", dataSource.Incidents, dataSource.CompanyId);

                ValidateInput("Sources", dataSource.Sources, dataSource.CompanyId);
            }

            if (dataSource.ReportType == ReportType.CoreReport || 
                dataSource.ReportType == ReportType.SummaryCoreReport)
            {
                ValidateInput("CorporateData", dataSource.CorporateData, dataSource.CompanyId);
                ValidateInput("FinancialData", dataSource.FinancialData, dataSource.CompanyId);

                ValidateInput("OverallPerformance", dataSource.OverallPerformance, dataSource.CompanyId);
                ValidateInput("IndustryTopPerformers", dataSource.IndustryTopPerformers, dataSource.CompanyId);
                ValidateInput("IndustryPeers", dataSource.IndustryPeers, dataSource.CompanyId);

                ValidateInput("CompanyHistoricalPerformance", dataSource.CompanyHistoricalPerformance, dataSource.CompanyId);

                ValidateInput("Analyst", dataSource.Analyst, dataSource.CompanyId);
                ValidateInput("SubPeerGroupKeyEsgIssues", dataSource.SubPeerGroupKeyEsgIssues, dataSource.CompanyId);
                ValidateInput("KeyEsgIssues", dataSource.KeyEsgIssues, dataSource.CompanyId);

                ValidateInput("IndicatorTemplates", dataSource.IndicatorTemplates, dataSource.CompanyId);

                ValidateInput("StandardIndicators", dataSource.StandardIndicators, dataSource.CompanyId);
                ValidateInput("ControversyIndicators", dataSource.ControversyIndicators, dataSource.CompanyId);

                ValidateInput("EventIndicators", dataSource.EventIndicators, dataSource.CompanyId);
                ValidateInput("Incidents", dataSource.Incidents, dataSource.CompanyId);
            }


            if (dataSource.ReportType == ReportType.ControversyReport)
            {
                ValidateInput("CorporateData", dataSource.CorporateData, dataSource.CompanyId);
                ValidateInput("FinancialData", dataSource.FinancialData, dataSource.CompanyId);

                ValidateInput("IndicatorTemplates", dataSource.IndicatorTemplates, dataSource.CompanyId);

                ValidateInput("ComparisonGroupControversies", dataSource.ComparisonGroupControversies, dataSource.CompanyId);
                ValidateInput("ControversyIndicators", dataSource.ControversyIndicators, dataSource.CompanyId);
                ValidateInput("EventIndicators", dataSource.EventIndicators, dataSource.CompanyId);
                ValidateInput("Incidents", dataSource.Incidents, dataSource.CompanyId);
            }

            _dataSource = dataSource;
            _useParallel = useParallel;
            _bulkGeneration = bulkGeneration;
            _generationResults = new List<GenerationResult>();
            _outlinePositions = new List<OutlinePosition>();
        }

        public byte[][] GetPdfDocument()
        {           
            var report = new PdfReport();
            var summaryReport = new PdfReport();

            GenerationResult companyReportSections = null;
            GenerationResult controversyReportSections = null;
            GenerationResult standardIndicatorsSection = null;
            GenerationResult controversiesSection = null;
            GenerationResult sourcesSection = null;
            GenerationResult parametersSection = null;
            PdfDocument pdfDocument;

            if (_dataSource.ReportType == ReportType.CoreReport)
            {
                if (_useParallel)
                {
                    Parallel.Invoke(
                        () => { companyReportSections = GetCompanyReportSections(); },
                        () => { standardIndicatorsSection = GetStandardIndicatorsSection(); },
                        () => { controversiesSection = GetControversiesSection(); },
                        () => { parametersSection = GetReportParametersSection(); }
                        );
                }
                else
                {
                    companyReportSections = GetCompanyReportSections();
                    standardIndicatorsSection = GetStandardIndicatorsSection();
                    controversiesSection = GetControversiesSection();
                    parametersSection = GetReportParametersSection();
                }
            }

            if (_dataSource.ReportType == ReportType.SummaryCoreReport && !_bulkGeneration)
            {
                if (_useParallel)
                {
                    Parallel.Invoke(
                        () => { companyReportSections = GetCompanyReportSections(); },
                        () => { parametersSection = GetReportParametersSection(); }
                        );
                }
                else
                {
                    companyReportSections = GetCompanyReportSections();
                    parametersSection = GetReportParametersSection();
                }
            }

            if (_dataSource.ReportType == ReportType.ComprehensiveReport)
            {
                if (_useParallel)
                {
                    Parallel.Invoke(
                        () => { companyReportSections = GetCompanyReportSections(); },
                        () => { standardIndicatorsSection = GetStandardIndicatorsSection(); },
                        () => { controversiesSection = GetControversiesSection(); },
                        () => { sourcesSection = GetSourcesSection(); },
                        () => { parametersSection = GetReportParametersSection(); }
                        );
                }
                else
                {
                    companyReportSections = GetCompanyReportSections();
                    standardIndicatorsSection = GetStandardIndicatorsSection();
                    controversiesSection = GetControversiesSection();
                    sourcesSection = GetSourcesSection();
                    parametersSection = GetReportParametersSection();
                }
            }

            if (_dataSource.ReportType == ReportType.SummaryComprehensiveReport && !_bulkGeneration)
            {
                if (_useParallel)
                {
                    Parallel.Invoke(
                        () => { companyReportSections = GetCompanyReportSections(); },
                        () => { parametersSection = GetReportParametersSection(); }
                        );
                }
                else
                {
                    companyReportSections = GetCompanyReportSections();
                    parametersSection = GetReportParametersSection();
                }
            }

            if (_dataSource.ReportType == ReportType.ControversyReport)
            {
                if (_useParallel)
                {
                    Parallel.Invoke(
                        () => { controversyReportSections = GetControversyReportSections(); },
                        () => { controversiesSection = GetControversiesSection(); },
                        () => { parametersSection = GetReportParametersSection(); }
                        );
                }
                else
                {
                    controversyReportSections = GetControversyReportSections();
                    controversiesSection = GetControversiesSection();
                    parametersSection = GetReportParametersSection();
                }
            }

            if (companyReportSections != null)
            {
                var pageOffset = report.PageCounter;

                pdfDocument = new PdfDocument(companyReportSections.PdfDocument);
                for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                {
                    report.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                }

                if (companyReportSections.OutlinePositions.Any())
                {
                    AddBookmarks(report, pageOffset, companyReportSections.OutlinePositions);
                }
            }

            if (companyReportSections != null && _bulkGeneration)
            {
                var pageOffset = summaryReport.PageCounter;

                var summaryPdfDocument = new PdfDocument(companyReportSections.PdfDocument);
                for (var page = 1; page <= summaryPdfDocument.Pages.Count; page++)
                {
                    summaryReport.AddPage(new ImportedPage(summaryPdfDocument.GetPage(page)));
                }

                if (companyReportSections.OutlinePositions.Any())
                {
                    AddBookmarks(summaryReport, pageOffset, companyReportSections.OutlinePositions);
                }
            }


            if (controversyReportSections != null)
            {
                var pageOffset = report.PageCounter;

                pdfDocument = new PdfDocument(controversyReportSections.PdfDocument);
                for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                {
                    report.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                }

                if (controversyReportSections.OutlinePositions.Any())
                {
                    AddBookmarks(report, pageOffset, controversyReportSections.OutlinePositions);
                }
            }

            var standardIndicatorsPageOffset = 0;

            if (standardIndicatorsSection != null)
            {
                standardIndicatorsPageOffset = report.PageCounter;
                var pageOffset = report.PageCounter;

                pdfDocument = new PdfDocument(standardIndicatorsSection.PdfDocument);
                for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                {
                    report.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                }

                if (standardIndicatorsSection.OutlinePositions.Any())
                {
                    AddBookmarks(report, pageOffset, standardIndicatorsSection.OutlinePositions);
                }

                if (companyReportSections != null && companyReportSections.PageElementPositions.Any())
                {
                    var parentOutlinePosition = standardIndicatorsSection.OutlinePositions[0];

                    var page = report.Document.Pages[0];

                    LinkThemeElements(page, pageOffset, companyReportSections.PageElementPositions,
                        parentOutlinePosition.ChildOutlinePositions);
                }
            }

            if (controversiesSection != null)
            {
                if (controversiesSection.PdfDocument != null)
                {
                    pdfDocument = new PdfDocument(controversiesSection.PdfDocument);

                    var pageOffset = report.PageCounter;

                    for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                    {
                        report.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                    }

                    if (controversiesSection.OutlinePositions.Any())
                    {
                        AddBookmarks(report, pageOffset, controversiesSection.OutlinePositions);


                        if (companyReportSections != null && companyReportSections.PageElementPositions.Any())
                        {
                            const float verticalPositionOffset = 650f + 12f + 21.5f;
                            var horizontalPositionOffset = report.LeftMargin;

                            var parentOutlinePosition = controversiesSection.OutlinePositions[0];

                            LinkQualitativePerformanceElements(report, pageOffset, 
                                horizontalPositionOffset, verticalPositionOffset,
                                companyReportSections.PageElementPositions, parentOutlinePosition.ChildOutlinePositions);
                        }

                        if (standardIndicatorsSection != null && standardIndicatorsSection.PageElementPositions.Any())
                        {
                            const float horizontalPositionOffset = 0.0f;
                            const float verticalPositionOffset = 0.0f;
                            var parentOutlinePosition = controversiesSection.OutlinePositions[0];

                            LinkEventIndicatorElements(report, standardIndicatorsPageOffset, pageOffset,
                                horizontalPositionOffset, verticalPositionOffset,
                                standardIndicatorsSection.PageElementPositions, parentOutlinePosition.ChildOutlinePositions);
                        }


                        if (controversyReportSections != null && controversyReportSections.PageElementPositions.Any())
                        {
                            const float horizontalPositionOffset = 0.0f;
                            const float verticalPositionOffset = 0.0f;
                            var parentOutlinePosition = controversiesSection.OutlinePositions[0];

                            LinkQualitativePerformanceElements(report, pageOffset, 
                                horizontalPositionOffset, verticalPositionOffset,
                                controversyReportSections.PageElementPositions, parentOutlinePosition.ChildOutlinePositions);

                            LinkEventIndicatorElements(report, 0, pageOffset,
                                horizontalPositionOffset, verticalPositionOffset,
                                controversyReportSections.PageElementPositions, parentOutlinePosition.ChildOutlinePositions);
                        }
                    }
                }
            }

            if (sourcesSection != null)
            {
                var pageOffset = report.PageCounter + 1;

                pdfDocument = new PdfDocument(sourcesSection.PdfDocument);
                for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                {
                    report.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                }

                report.ParentOutline.ChildOutlines.Add("Sources", new XYDestination(pageOffset, 0, 0));
            }


            if (parametersSection != null)
            {
                pdfDocument = new PdfDocument(parametersSection.PdfDocument);
                for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                {
                    report.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                }
            }

            if (parametersSection != null && _bulkGeneration)
            {
                pdfDocument = new PdfDocument(parametersSection.PdfDocument);
                for (var page = 1; page <= pdfDocument.Pages.Count; page++)
                {
                    summaryReport.AddPage(new ImportedPage(pdfDocument.GetPage(page)));
                }
            }


            AddPageFooter(report);
            if (_bulkGeneration)
            {
                AddPageFooter(summaryReport);
            }

            var renderedReports = new List<byte[]>
            {
                report.Draw()
            };

            if (_bulkGeneration)
            {
                renderedReports.Add(summaryReport.Draw());
            }

            return renderedReports.ToArray();
        }


        private void AddBookmarks(PdfReport report, int pageOffset, IEnumerable<OutlinePosition> outlinePositions)
        {
            if (_dataSource.ReportType == ReportType.CoreReport || 
                _dataSource.ReportType == ReportType.SummaryCoreReport) return;

            foreach (var parentOutlinePosition in outlinePositions)
            {
                var parentOutline = report.ParentOutline.ChildOutlines.Add(parentOutlinePosition.Name,
                    new XYDestination(parentOutlinePosition.PageNumber + pageOffset, 0.0f,
                        parentOutlinePosition.VerticalPosition));

                if (parentOutlinePosition.ChildOutlinePositions == null ||
                    !parentOutlinePosition.ChildOutlinePositions.Any()) continue;

                foreach (var childOutlinePosition in parentOutlinePosition.ChildOutlinePositions)
                {
                    var childOutline = parentOutline.ChildOutlines.Add(childOutlinePosition.Name,
                        new XYDestination(childOutlinePosition.PageNumber + pageOffset, 0,
                            childOutlinePosition.VerticalPosition));

                    if (childOutlinePosition.ChildOutlinePositions == null ||
                    !childOutlinePosition.ChildOutlinePositions.Any()) continue;

                    foreach (var subChildOutlinePosition in childOutlinePosition.ChildOutlinePositions)
                    {
                        childOutline.ChildOutlines.Add(subChildOutlinePosition.Name,
                            new XYDestination(subChildOutlinePosition.PageNumber + pageOffset, 0,
                                subChildOutlinePosition.VerticalPosition));
                    }
                }
            }
        }

        private void LinkQualitativePerformanceElements(PdfReport report, int pageOffset, 
            float horizontalPositionOffset, float verticalPositionOffset,
            List<PageElementPosition> pageElementPositions, List<OutlinePosition> outlinePositions)
        {
            if (!_dataSource.EventIndicators.Any()) return;

            var qualitativePerformance = _dataSource.EventIndicators
                .Where(ei => ei.AnswerCategory > 0)
                .Select(ei => new
                {
                    ei.AnswerCategory,
                    ei.BaseEventIndicatorId,
                    BaseEventIndicatorName = ei.Name,
                    ei.ControversyIndicatorId,
                    ei.ControversyIndicatorName,
                    ei.ControversyIndicatorSortKey
                })
                .ToList();

            for (var awnswerCategory = 1; awnswerCategory <= 5; awnswerCategory++)
            {
                var eventIndicators =
                    qualitativePerformance.Where(qp => qp.AnswerCategory == awnswerCategory)
                        .OrderBy(qp => qp.ControversyIndicatorSortKey).ToList();

                if (!eventIndicators.Any()) continue;

                foreach (var eventIndicator in eventIndicators.OrderBy(ei => ei.BaseEventIndicatorName))
                {
                    var eventIndicatorPositions = pageElementPositions
                        .Where(pep => pep.PageNumber < 2 && pep.Name == eventIndicator.BaseEventIndicatorName)
                        .ToList();

                    if (!eventIndicatorPositions.Any()) continue;

                    foreach (var eventIndicatorPosition in eventIndicatorPositions)
                    {
                        var controversyIndicatorOutline = outlinePositions
                            .SingleOrDefault(op => op.Id == eventIndicator.ControversyIndicatorId);

                        if (controversyIndicatorOutline == null) continue;

                        var eventIndicatorOutline = controversyIndicatorOutline.ChildOutlinePositions
                            .SingleOrDefault(op => op.Id == eventIndicator.BaseEventIndicatorId);

                        if (eventIndicatorOutline == null || eventIndicatorPosition == null) continue;

                        var page = report.Document.Pages[0];

                        page.Elements.Add(
                            new Link(eventIndicatorPosition.HorizontalPosition + horizontalPositionOffset,
                                eventIndicatorPosition.VerticalPosition + verticalPositionOffset,
                                eventIndicatorPosition.Width, eventIndicatorPosition.Height,
                                new XYDestination(eventIndicatorOutline.PageNumber + pageOffset, 0,
                                    eventIndicatorOutline.VerticalPosition)));
                    }
                }
            }
        }

        private void LinkEventIndicatorElements(PdfReport report, int standardIndicatorsPageOffset, int eventIndicatorsPageOffset,
            float horizontalPositionOffset, float verticalPositionOffset,
            List<PageElementPosition> pageElementPositions, List<OutlinePosition> outlinePositions)
        {
            if (!_dataSource.IndicatorTemplates.Any() || !_dataSource.ControversyIndicators.Any()) return;

            foreach (var templateIndicator in _dataSource.IndicatorTemplates.Where(i => i.IsControversyIndicator).OrderBy(i => i.SortKey))
            {
                var controversyIndicator = _dataSource.ControversyIndicators.SingleOrDefault(ci => ci.Template.Id == templateIndicator.Id);

                if (controversyIndicator == null) continue;

                var controversyIndicatorOutline = outlinePositions
                    .SingleOrDefault(op => op.Id == controversyIndicator.Template.Id);

                if (controversyIndicatorOutline == null) continue;
                if (!controversyIndicator.EventIndicators.Any()) continue;

                foreach (var eventIndicator in controversyIndicator.EventIndicators)
                {
                    var elementPosition = pageElementPositions
                        .SingleOrDefault(pep => pep.PageNumber > 1 && pep.Name == eventIndicator.Name);

                    var eventIndicatorOutline = controversyIndicatorOutline.ChildOutlinePositions
                        .SingleOrDefault(op => op.Id == eventIndicator.Id);

                    if (eventIndicatorOutline == null || elementPosition == null) continue;

                    var pageNumber = elementPosition.PageNumber + standardIndicatorsPageOffset;

                    var page = report.Document.Pages[pageNumber - 1];

                    var destinationPageNumber = eventIndicatorOutline.PageNumber + eventIndicatorsPageOffset;
                    var destinationVerticalPosition = eventIndicatorOutline.VerticalPosition;

                    page.Elements.Add(
                        new Link(elementPosition.HorizontalPosition + horizontalPositionOffset,
                            elementPosition.VerticalPosition + verticalPositionOffset,
                            elementPosition.Width, elementPosition.Height,
                            new XYDestination(destinationPageNumber, 0, destinationVerticalPosition)));
                }
            }
        }

        private static void LinkThemeElements(Page page, int pageOffset,
            IEnumerable<PageElementPosition> pageElementPositions, IEnumerable<OutlinePosition> outlinePositions)
        {
            var themes = new List<string> {"Environment", "Social", "Governance"};

            themes.ForEach(theme =>
            {
                var themePosition = pageElementPositions
                    .SingleOrDefault(pep => pep.Name == theme);

                var themeOutline = outlinePositions
                    .SingleOrDefault(op => op.Name == theme);

                if (themeOutline == null || themePosition == null) return;

                page.Elements.Add(new Link(themePosition.HorizontalPosition,
                    themePosition.VerticalPosition, themePosition.Width, themePosition.Height,
                        new XYDestination(themeOutline.PageNumber + pageOffset, 0, themeOutline.VerticalPosition)));
            });
        }

        private GenerationResult GetCompanyReportSections()
        {
            var documentGenerator = new CompanyReportSections();

            return documentGenerator.GeneratePdfDocument(_dataSource);
        }

        private GenerationResult GetControversyReportSections()
        {
            var documentGenerator = new ControversyReportSections();

            return documentGenerator.GeneratePdfDocument(_dataSource);
        }

        private GenerationResult GetStandardIndicatorsSection()
        {
            var documentGenerator = new StandardIndicatorsSection();

            return documentGenerator.GeneratePdfDocument(_dataSource);
        }

        private GenerationResult GetControversiesSection()
        {
            var documentGenerator = new ControversiesSection();

            return documentGenerator.GeneratePdfDocument(_dataSource);
        }

        private GenerationResult GetSourcesSection()
        {
            var documentGenerator = new SourcesSection();

            return documentGenerator.GeneratePdfDocument(_dataSource);
        }

        private GenerationResult GetReportParametersSection()
        {
            var parametersSection = new ReportParametersSection();

            return parametersSection.GeneratePdfDocument(_dataSource);
        }

        private void AddPageFooter(PdfReport report)
        {
            var pageNumber = 1;
            var pageCount = report.Document.Pages.Count;

            for (var i = 0; i < report.Document.Pages.Count; i++)
            {
                var page = report.Document.Pages[i];

                if (pageNumber > 1)
                {
                    page.Elements.Add(new Line(100, 790, 520, 790, 0.5f, RgbColor.Gray));
                    page.Elements.Add(new Image(ImageData.GetImage(CompanyLogoFooterImage),
                        report.LeftMargin + 85f, 781, 0.24f) { Align = Align.Right, VAlign = VAlign.Top });
                }
                else
                {
                    page.Elements.Add(new Line(report.LeftMargin, 790, 520, 790, 0.5f, RgbColor.Gray));
                }

                page.Elements.Add(new Label(string.Format("{0} | {1}", pageNumber, pageCount),
                    500, 785, 50, 10, Fonts.CalibriRegular, 10, TextAlign.Right, RgbColor.DimGray));

                pageNumber++;
            }
        }
    }
}
